package il.ac.tau.cvws.scrabblesidekick.server.engine.scrabble;

import il.ac.tau.cvws.scrabblesidekick.server.engine.scrabble.detector.HorizontalSolutionDetector;
import il.ac.tau.cvws.scrabblesidekick.server.engine.scrabble.detector.SolutionDetector;
import il.ac.tau.cvws.scrabblesidekick.server.engine.scrabble.detector.VerticalSolutionDetector;
import il.ac.tau.cvws.scrabblesidekick.server.engine.scrabble.exception.ScrabbleException;
import il.ac.tau.cvws.scrabblesidekick.server.util.exception.ExceptionConstant;
import il.ac.tau.cvws.scrabblesidekick.server.util.exception.ScrabbleSidekickException;
import il.ac.tau.cvws.scrabblesidekick.shared.datatype.WordPlacement;
import il.ac.tau.cvws.scrabblesidekick.shared.datatype.game.Alphabet;
import il.ac.tau.cvws.scrabblesidekick.shared.datatype.game.Board;
import il.ac.tau.cvws.scrabblesidekick.shared.datatype.game.GameData;
import il.ac.tau.cvws.scrabblesidekick.shared.datatype.game.Position;
import il.ac.tau.cvws.scrabblesidekick.shared.datatype.game.Rack;
import il.ac.tau.cvws.scrabblesidekick.shared.datatype.game.TileLayout;

import java.util.HashSet;
import java.util.List;
import java.util.Set;

public class ScrabbleSolver
{
	/*
	 *LOGIC: the function finds the maximal word taken into account the current layout on game board, the user's rack and the used dictionary.
	 * first the functions calls  findTileLayoutBoundries to find the boundries of searching and then the function calls solveScrabbleGame that retrive the solution.
	 * INPUT: gameData- the current gameData which received from the client
	 * RETURN: wordPlacement- the solution.
	 */
	public WordPlacement solveGame(GameData gameData) throws ScrabbleException
	{
		WordPlacement sol = null;

		try
		{
			Set<Position> boundries = findTileLayoutBoundaries(gameData.getBoard().getLayout());
			
			sol = solveScrabbleGame(boundries, gameData.getBoard().getLayout(), gameData.getRack(), gameData.getDictionaryId(), gameData.getAlphabet());			
		}
		catch(Exception e)
		{
			e.printStackTrace();
			throw new ScrabbleException(ExceptionConstant.GENERAL_FAILURE, e);
		}

		return sol;
	}

	/*
	 * LOGIC: this function search for the maximal solution by getting all the possible horizontal solutions and then, all the possible vertical solutions. 
	 * INPUT: layoutBoundries - area of search(where there are still tiles), rack- the user's rack, dictionaryId- the identifier of the dictioanry, alphabet- the alphabet letters and values corresponded.
	 * RETURN: the function returns a the maximal found solution.
	 */
	private WordPlacement solveScrabbleGame(Set<Position> layoutBoundries, TileLayout layout, Rack rack, int dicitionaryId, Alphabet alephbeit) throws ScrabbleSidekickException
	{
		SolutionDetector horDetector = new HorizontalSolutionDetector();
		SolutionDetector verDetector = new VerticalSolutionDetector();
		
		List<WordPlacement> solHor = horDetector.detect(layoutBoundries, layout, dicitionaryId, rack);
		List<WordPlacement> solVer = verDetector.detect(layoutBoundries, layout, dicitionaryId, rack);

		return findMaximalSol(solHor, solVer, layout, alephbeit);
	}
	/*
	 * LOGIC: this function iterate over all the vertical and horizontal suggestions and looks for the maximal by calculating the sum of letters values.
	 * INPUT: solHor- horizontal solutions, solVer- vertical solutions, layout- current game layout on board, alphabet- map from letters to their values.
	 * RETURN: suggestion with maximal value.
	 */
	private WordPlacement findMaximalSol(List<WordPlacement> solHor,List<WordPlacement> solVer, TileLayout layout, Alphabet alphabet) 
	{
		int grade 			 = 0;
		WordPlacement maxSol = null;

		if (solHor != null)
		{
			for (WordPlacement sol : solHor)
			{
				int tempGrade = 0;

				for (int letter: sol.getWord())
				{
					tempGrade += alphabet.getValuePerTile(letter);
				}
				
				if(tempGrade > grade)
				{
					grade = tempGrade;
					maxSol = sol;
				}
			}
		}
		
		if (solVer != null)
		{
			for (WordPlacement sol : solVer)
			{
				int tempGrade = 0;

				for(int letter: sol.getWord())
				{
					tempGrade += alphabet.getValuePerTile(letter);
				}
				
				if(tempGrade > grade)
				{
					grade = tempGrade;
					maxSol = sol;
				}
			}
		}
		
		return maxSol;
	}

	/*
	 * LOGIC: the function finds the boundaries of the given layout. the function iterates over each tile in layout and checks whether the tile has an adjacent empty tile from the 
	 * left/right/up/down direction. if the tile has an adjacent empty tile the tile will be added as a boundry.
	 * INPUT: layout- the description of the tiles on board.
	 * RETURN: a set of the boundary tiles in layout
	 */
	private Set<Position> findTileLayoutBoundaries(TileLayout layout)
	{
		Set<Position> boundryTempSet = new HashSet<Position>();
		Set<Position> boundrySet = new HashSet<Position>();

		Set<Position> layoutPositions = layout.asPostitionSet();

		for (Position p: layoutPositions)
		{
			boundryTempSet.add(new Position(p.getRow() + 1, p.getColumn()));
			boundryTempSet.add(new Position(p.getRow() - 1, p.getColumn()));
			boundryTempSet.add(new Position(p.getRow(), p.getColumn() + 1));
			boundryTempSet.add(new Position(p.getRow(), p.getColumn() - 1));
		}

		for (Position p1: boundryTempSet)
		{
			if (p1.getColumn() < Board.SQUARES_NUM && p1.getColumn() >= 0 && p1.getRow() < Board.SQUARES_NUM && p1.getRow() >= 0)
			{
				if (layout.isPosInLayout(p1) == false)
				{
					boundrySet.add(p1);
				}
			}
		}

		return boundrySet;
	}
}
